跳到主要内容

EasyHook 学习

尝试逆向 libcef 的初始化

首先我们分析源码可以知道,cef 初始化是使用

CEF_EXPORT int cef_initialize(const cef_main_args_t* args,
const struct _cef_settings_t* settings,
cef_app_t* application,
void* windows_sandbox_info);

这个函数来初始化的,我们可以通过 hook 这个函数来修改一些参数,比如说调试端口。

但是看入参,可以发现 cef 注册回调时是整个结构体 _cef_settings_t 的,必须找到对应的版本避免新版本结构体不一样导致偏移位置有差异。

所以我们需要找到对应版本的 libcef 源码,找到对应的结构体偏移位置,然后修改对应的值。

这里看这个版本是 108.4.13.0

提示

要找到 CEF 108.4.13.0 版本的 libcef 源码,你可以按照以下步骤操作:

  1. 访问 CEF 官方下载页面:https://cef-builds.spotifycdn.com/index.html

  2. 在页面上找到 108.4.13+gc8a8ced+chromium-108.0.5359.125 版本。这个版本对应的 Chromium 版本是 108.0.5359.125。

需要注意的是,CEF 仓库本身并不包含完整的 libcef 源码。它主要包含了 CEF 的 API 定义和一些辅助文件。完整的 libcef 实现依赖于特定版本的 Chromium 源码。

CEF 的 GitHub 仓库:https://github.com/chromiumembedded/cef chromium 的 GitHub 仓库:https://chromium.googlesource.com/chromium/src.git

可以在下面这里找到 remote_debugging_port 的定义

include\internal\cef_types.h#L385

去掉注释就是下面的内容

对照源码中结构体计算偏移值

我们可以这样计算 remote_debugging_port 的偏移:

typedef struct _cef_settings_t {
size_t size;
int single_process;
int no_sandbox;
cef_string_t browser_subprocess_path;
int multi_threaded_message_loop;
int multi_threaded_message_loop;
int windowless_rendering_enabled;
int command_line_args_disabled;
cef_string_t cache_path;
int persist_session_cookies;
cef_string_t user_agent;
cef_string_t product_version;
cef_string_t locale;
cef_string_t log_file;
cef_severity_t log_severity;
cef_string_t javascript_flags;
cef_string_t resources_dir_path;
cef_string_t locales_dir_path;
int pack_loading_disabled;
int remote_debugging_port;
// ...后续字段省略
} cef_settings_t;

计算偏移:

1. size: 8字节
2. single_process: 4字节
3. no_sandbox: 4字节
4. browser_subprocess_path: 24字节
5. multi_threaded_message_loop: 4字节
6. windowless_rendering_enabled: 4字节
7. command_line_args_disabled: 4字节
8. cache_path: 24字节
9. persist_session_cookies: 4字节
10. user_agent: 24字节
11. product_version: 24字节
12. locale: 24字节
13. log_file: 24字节
14. log_severity: 4字节
15. javascript_flags: 24字节
16. resources_dir_path: 24字节
17. locales_dir_path: 24字节
18. pack_loading_disabled: 4字节
19. remote_debugging_port: 4字节

其中 cef_string_t 的大小需要查看具体定义,通常是 24 字节(包含指针、长度和预留空间)。

所以 remote_debugging_port 的偏移量为:

8 + 4 + 4 + 24 + 4 + 4 + 4 + 24 + 4 + 24 + 24 + 24 + 24 + 4 + 24 + 24 + 24 + 4 = 256

这样就能正确定位到 remote_debugging_port 字段了。

// 修改 Debug Port 为9222
void SetDebugPort(BYTE *pbFileData)
{
if (!memcmp(pbFileData + 0x14EED, bSettingAsm, sizeof(bSettingAsm)))
{
bSettingAsm[2] = 0x94; // 结构体偏移
bSettingAsm[6] = 0x06; // 0x2406 也就是 9222端口
bSettingAsm[7] = 0x24;
memcpy(pbFileData + 0x14EED, bSettingAsm, sizeof(bSettingAsm));
}
}

这段代码是在修改一条汇编指令,以便在运行时修改 CEF 设置结构体中的 remote_debugging_port 字段。通过精确计算偏移量,我们可以确保修改正确的内存位置。

提示

1、bSettingAsm 是什么: 这通常是一个字节数组,用于存储要写入或修改的汇编指令或数据。在你的代码中,它可能是这样定义的:

BYTE bSettingAsm[] = { 0x41, 0xC7, 0x84, 0x24, 0x00, 0x00, 0x00, 0x00 };

这个数组代表了一条汇编指令,用于修改内存中的某个值。

2、低字节和高字节:

在计算机中,数据通常以字节为单位存储。对于多字节的数据(如16位、32位整数),会分为低字节和高字节。

  • 低字节:数值的最低 8 位
  • 高字节:数值的次低 8 位

例如,对于16位数值 0x1234:

  • 0x34 是低字节
  • 0x12 是高字节

3、在上面的代码中:

bSettingAsm[2] = 0x94; // 结构体偏移
bSettingAsm[6] = 0x06; // 0x2406 也就是 9222 端口
bSettingAsm[7] = 0x24;

这里是在修改汇编指令中的特定字节:

  • bSettingAsm[2] = 0x94; 设置偏移量的低字节
  • bSettingAsm[6] = 0x06;bSettingAsm[7] = 0x24; 设置端口号 9222 (0x2406)

4、bSettingAsm 这些索引实际上对应于一条特定的汇编指令的不同部分

首先,假设 bSettingAsm 数组代表了以下的汇编指令:

mov dword ptr ds:[ebx + 0x94], 0x2406

假设这条指令在 x64 汇编中的机器码是:

41 C7 84 24 94 00 00 00 06 24 00 00

现在让我们逐个解析这些索引:

  1. bSettingAsm[2] = 0x94;

    • 索引 2 对应指令中的偏移量部分。
    • 0x94 是结构体成员的偏移量。
  2. bSettingAsm[6] = 0x06;bSettingAsm[7] = 0x24;

    • 索引 6 和 7 对应指令中要写入的值(0x2406,即 9222)。
    • 注意这里使用的是小端序,所以 0x06 在前,0x24 在后。

完整解析:

  • 索引 0-1:41 C7 是指令的操作码
  • 索引 2-5:84 24 94 00 是指令的内存偏移量部分
  • 索引 6-9:00 00 00 06 是要写入的值的低字节(即上面的 0x2406)

通过修改这些特定的字节,代码可以动态地改变:

  1. 结构体成员的偏移量(通过索引 2)
  2. 要设置的调试端口值(通过索引 6 和 7)

这种方法允许在运行时精确地修改内存中的特定值,而不需要重新编译整个程序。但是,这种方法也很脆弱,因为它依赖于特定的汇编指令格式和内存布局,可能在不同的编译器设置或 CEF 版本中失效。

References